package com.generator.code;


import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.ClassUtil;
import cn.hutool.core.util.StrUtil;
import cn.smallbun.screw.core.metadata.Column;
import cn.smallbun.screw.core.query.DatabaseQuery;
import cn.smallbun.screw.core.query.DatabaseQueryFactory;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.baomidou.mybatisplus.generator.config.po.TableField;
import com.baomidou.mybatisplus.generator.config.po.TableInfo;
import com.baomidou.mybatisplus.generator.config.rules.DateType;
import com.baomidou.mybatisplus.generator.config.rules.DbColumnType;
import com.baomidou.mybatisplus.generator.config.rules.IColumnType;
import com.github.davidfantasy.mybatisplus.generatorui.GeneratorConfig;
import com.github.davidfantasy.mybatisplus.generatorui.MybatisPlusToolsApplication;
import com.github.davidfantasy.mybatisplus.generatorui.common.ServiceException;
import com.github.davidfantasy.mybatisplus.generatorui.dto.Constant;
import com.github.davidfantasy.mybatisplus.generatorui.dto.OutputFileInfo;
import com.github.davidfantasy.mybatisplus.generatorui.dto.UserConfig;
import com.github.davidfantasy.mybatisplus.generatorui.mbp.NameConverter;
import com.github.davidfantasy.mybatisplus.generatorui.mbp.TemplateVaribleInjecter;
import com.github.davidfantasy.mybatisplus.generatorui.util.JsonUtil;
import com.github.davidfantasy.mybatisplus.generatorui.util.PathUtil;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.common.io.Files;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.sql.DataSource;
import java.awt.*;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.URI;
import java.nio.charset.Charset;
import java.util.List;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import static com.github.davidfantasy.mybatisplus.generatorui.dto.Constant.*;

/**
 * 代码自动生成
 */
public class GeberatorUIServer {

    private static Map<String, ? extends List<? extends Column>> stringListMap = null;

    public static void main(String[] args) throws IOException {
        String jdbcUrl = args.length > 0 ? args[0] : "jdbc:mysql://x.dev.huahuihr.com:13306/hr-system";
        jdbcUrl += "?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT%2B8";
        GeneratorConfig config = GeneratorConfig.builder()
                .jdbcUrl(jdbcUrl)
                .userName("root")
                .password("dCXlZb6x9II0*3CQ")
                .templateVaribleInjecter(new MyTemplateVaribleInjecter())
                // 实体类不去掉表前缀
                .nameConverter(new MyNameConverter(false))
                .driverClassName("com.mysql.cj.jdbc.Driver")
                .basePackage("com.generator.code")
                .port(8068)
                .dateType(DateType.ONLY_DATE)
                .idType(IdType.ASSIGN_UUID)
                .build();

        // 获取所有表中的列
        getTables(config);
        // 启动
        MybatisPlusToolsApplication.run(config);
        // 生成默认配置
        MainConfig.saveDefaultStrategy(config);
        // 设置无外设的情况下，启动桌面应用参数
        System.setProperty("java.awt.headless", "false");
        if (Desktop.isDesktopSupported()) {
            // 启动系统默认的游览器
            Desktop desktop = Desktop.getDesktop();
            desktop.browse(URI.create("http://localhost:8068/table"));
        }
    }

    private static void getTables(GeneratorConfig config) {
        //获取query对象
        DatabaseQuery query = new DatabaseQueryFactory(getDataSource(config)).newInstance();
        //获取全部表
        List<? extends Column> columns = query.getTableColumns();
        stringListMap = columns.stream().collect(Collectors.groupingBy(Column::getTableName));
    }

    /**
     * 获取数据库源
     */
    private static DataSource getDataSource(GeneratorConfig config) {
        //数据源
        HikariConfig hikariConfig = new HikariConfig();
        hikariConfig.setDriverClassName(config.getDriverClassName());
        hikariConfig.setJdbcUrl(config.getJdbcUrl());
        hikariConfig.setUsername(config.getUserName());
        hikariConfig.setPassword(config.getPassword());
        //设置可以获取tables remarks信息
        hikariConfig.addDataSourceProperty("useInformationSchema", "true");
        hikariConfig.setMinimumIdle(2);
        hikariConfig.setMaximumPoolSize(5);
        return new HikariDataSource(hikariConfig);
    }

    public static List<? extends Column> getStringListMap(String tableName) {
        return stringListMap.get(tableName);
    }
}

/**
 * 自定义文件名、类名
 */
class MyNameConverter implements NameConverter {

    private Boolean tablePrefix = true;

    public MyNameConverter() {
    }

    public MyNameConverter(Boolean tablePrefix) {
        this.tablePrefix = tablePrefix;
    }

    @Override
    public String entityNameConvert(String tableName) {
        if (Strings.isNullOrEmpty(tableName)) {
            return "";
        }
        if (tablePrefix) {
            tableName = tableName.substring(tableName.indexOf(StrUtil.UNDERLINE) + 1, tableName.length());
        }
        return StrUtil.upperFirst(StrUtil.toCamelCase(tableName.toLowerCase()));
    }

    @Override
    public String outputFileNameConvert(String fileType, String tableName) {
        if (fileType.equals(Constant.FILE_TYPE_ENTITY)) {
            return this.entityNameConvert(tableName) + DOT_JAVA;
        } else if (fileType.equals(Constant.FILE_TYPE_MAPPER)) {
            return this.mapperNameConvert(tableName) + DOT_JAVA;
        } else if (fileType.equals(Constant.FILE_TYPE_MAPPER_XML)) {
            return this.mapperXmlNameConvert(tableName) + DOT_XML;
        } else if (fileType.equals(Constant.FILE_TYPE_SERVICE)) {
            return this.entityNameConvert(tableName) + "Service" + DOT_JAVA;
        } else if (fileType.equals(Constant.FILE_TYPE_SERVICEIMPL)) {
            return this.serviceImplNameConvert(tableName) + DOT_JAVA;
        } else if (fileType.equals(Constant.FILE_TYPE_CONTROLLER)) {
            return this.controllerNameConvert(tableName) + DOT_JAVA;
        } else if (fileType.equals(MyConstant.FILE_TYPE_DTO)) {
            return this.entityNameConvert(tableName) + MyConstant.FILE_TYPE_DTO + DOT_JAVA;
        } else if (fileType.equals(MyConstant.FILE_TYPE_VO)) {
            return this.entityNameConvert(tableName) + MyConstant.FILE_TYPE_VO + DOT_JAVA;
        }
        return this.entityNameConvert(tableName) + fileType;
    }
}

/**
 * 自定义模板参数
 */
class MyTemplateVaribleInjecter implements TemplateVaribleInjecter {

    @Override
    public Map<String, Object> getCustomTemplateVaribles(TableInfo tableInfo) {
        tableInfo.setServiceName(tableInfo.getEntityName() + "Service");
        Map<String, Object> params = Maps.newHashMap();
        // 定义DTO 的包名
        params.put("packageDTO", "com.generator.code.dto");
        params.put("packageVO", "com.generator.code.vo");
        // 定义DTO的后缀
        params.put("dtoEntity", MyConstant.FILE_TYPE_DTO);
        // 定义 不想导入到DTO 中的 包的关键字集合
        params.put("excludeList", Lists.newArrayList("com.baomidou.mybatisplus.annotation",
                "com.icp.common.mybatis.entity.BaseEntity", "com.icp.common.mybatis.entity.BaseBean"));
        // 解析提交校验注解
        List<TableField> fields = tableInfo.getFields();
        Boolean importFlag = true;
        List<? extends Column> columns = GeberatorUIServer.getStringListMap(tableInfo.getName());
        Set<String> columnNames = Sets.newHashSet();
        if (CollUtil.isNotEmpty(columns)) {
            columnNames = columns.stream().filter(cs -> "0".equals(((Column) cs).getNullable()))
                    .map(Column::getColumnName).collect(Collectors.toSet());
            boolean suplerFlag = columns.stream()
                    .filter(cls -> ((Column) cls).getColumnName().equals("djhxgdwmc"))
                    .findAny().isPresent();
            boolean suplerFlagNew = columns.stream()
                    .filter(cls -> ((Column) cls).getColumnName().equals("xgdwmc"))
                    .findAny().isPresent();
            params.put("suplerFlag", suplerFlag);
            params.put("suplerFlagNew", suplerFlagNew);
            if (suplerFlagNew) {
                params.put("baseEntityInfo", "BaseBean");
                tableInfo.getImportPackages().add("com.icp.common.mybatis.entity.BaseBean");
            }

        }
        for (TableField field : fields) {
            IColumnType columnType = field.getColumnType();
            String comment = Optional.ofNullable(field.getComment()).orElse("");
            if (DbColumnType.STRING.getType().equals(columnType.getType()) && !field.getName().equals("id")) {
                String number = getNumber(field.getType());
                if (StringUtils.isBlank(number) || Integer.parseInt(number) <= 0) {
                    continue;
                }
                String str0 = "@Length(max = " + number + ", message = \"" + comment + "长度不能超过{max}\")";
                String str1 = "@NotBlank(message = \"" + comment + "不能为空\")";
                String str2 = ", allowableValues = \"range[0, " + number + "]\"";
                Map<String, Object> map = Maps.newHashMap();
                map.put("length", str0);
                map.put("allowableValues", str2);
                if (columnNames.contains(field.getName())) {
                    map.put("notBlank", str1);
                    if (importFlag) {
                        tableInfo.getImportPackages().add("javax.validation.constraints.NotBlank");
                    }
                }
                field.setCustomMap(map);
                if (importFlag) {
                    tableInfo.getImportPackages().add("org.hibernate.validator.constraints.Length");
                }

            }
        }
        return params;
    }


    private String getNumber(String source) {
        String regEx = "[^0-9]";
        Pattern p = Pattern.compile(regEx);
        Matcher m = p.matcher(source);
        return m.replaceAll("").trim();
    }
}

/**
 * 初始化配置
 */
class MainConfig {

    private final static Logger LOGGER = LoggerFactory.getLogger(MainConfig.class);

    /**
     * 初始化配置
     *
     * @param generatorConfig
     * @throws IOException
     */
    public static void saveDefaultStrategy(GeneratorConfig generatorConfig) throws IOException {
        String storeDir = PathUtil.joinPath(System.getProperty("user.home"), CONFIG_HOME, generatorConfig.getBasePackage());
        // 是否初始化配置
        if (checkUserConfigExisted(storeDir)) {
            return;
        }
        // 读取默认配置
        String path = ClassUtil.getClassPath() + File.separator + "user-config.json";
        UserConfig userConfig = MainConfig.getUserConfigFromFile(path);
        // 添加dto 文件
        OutputFileInfo dtoFile = getOutputFileInfo(generatorConfig, storeDir);
        userConfig.getOutputFiles().add(dtoFile);
        // 添加VO 文件
        OutputFileInfo voFile = getOutputFileInfoVO(generatorConfig, storeDir);
        userConfig.getOutputFiles().add(voFile);
        // 初始化输出文件
        String userConfigPath = storeDir + File.separator + "user-config.json";
        saveUserConfig(userConfig, userConfigPath);
        LOGGER.info("---------初始化默认配置成功------------");
    }

    private static OutputFileInfo getOutputFileInfo(GeneratorConfig generatorConfig, String storeDir) {
        OutputFileInfo dtoFile = new OutputFileInfo();
        dtoFile.setBuiltIn(true);
        dtoFile.setFileType(MyConstant.FILE_TYPE_DTO);
        dtoFile.setOutputLocation(generatorConfig.getBasePackage() + StringPool.DOT + "dto");
        dtoFile.setTemplateName(MyConstant.DTO_TEMPLATE_NAME);
        dtoFile.setTemplatePath(uploadTemplate(storeDir, MyConstant.DTO_TEMPLATE_NAME));
        return dtoFile;
    }

    private static OutputFileInfo getOutputFileInfoVO(GeneratorConfig generatorConfig, String storeDir) {
        OutputFileInfo dtoFile = new OutputFileInfo();
        dtoFile.setBuiltIn(true);
        dtoFile.setFileType(MyConstant.FILE_TYPE_VO);
        dtoFile.setOutputLocation(generatorConfig.getBasePackage() + StringPool.DOT + "vo");
        dtoFile.setTemplateName(MyConstant.VO_TEMPLATE_NAME);
        dtoFile.setTemplatePath(uploadTemplate(storeDir, MyConstant.VO_TEMPLATE_NAME));
        return dtoFile;
    }

    private static boolean checkUserConfigExisted(String storeDir) {
        if (!FileUtil.exist(storeDir)) {
            return false;
        }
        int compare = DateUtil.compare(DateUtil.date(FileUtil.file(storeDir).lastModified()),
                DateUtil.parseDateTime("2021-11-27 00:00:00"));
        if (compare <= 0) {
            return false;
        }
        return true;
    }

    private static UserConfig getUserConfigFromFile(String userConfigPath) {
        if (!FileUtil.exist(userConfigPath)) {
            return null;
        }
        String userConfigStr = FileUtil.readString(userConfigPath, Charset.forName("utf-8"));
        try {
            return JsonUtil.json2obj(userConfigStr, UserConfig.class);
        } catch (Exception e) {
            LOGGER.error("读取默认配置文件发生错误：", e);
            return null;
        }
    }

    private static String uploadTemplate(String storeDir, String filedes) {
        String templatePath = PathUtil.joinPath(ClassUtil.getClassPath() + "codetpls", filedes);
        File templateFile = new File(templatePath);
        String fileName = "";
        if (templateFile.exists()) {
            fileName = templateFile.getName();
        }
        String fileSuffix = fileName.substring(fileName.lastIndexOf(".") + 1);
        String saveFileName = fileName.substring(0, fileName.lastIndexOf(fileSuffix)) + DateUtil.format(new Date(), "yyyyMMddHHmmss");
        String savePath = PathUtil.joinPath(storeDir, TEMPLATE_STORE_DIR, saveFileName);
        File saveFile = new File(savePath);
        try {
            FileUtil.writeFromStream(new FileInputStream(templateFile), saveFile);
        } catch (IOException e) {
            throw new ServiceException("上传模板文件失败", e);
        }
        return RESOURCE_PREFIX_FILE + savePath;
    }

    private static void saveUserConfig(UserConfig userConfig, String userConfigPath) throws IOException {
        if (userConfig == null) {
            throw new ServiceException("不能写入空的用户配置");
        }
        String configStr = JsonUtil.obj2json(userConfig);
        File userConfigFile = new File(userConfigPath);
        if (userConfigFile.exists()) {
            userConfigFile.delete();
        }
        Files.createParentDirs(userConfigFile);
        userConfigFile.createNewFile();
        FileUtil.writeFromStream(new ByteArrayInputStream(configStr.getBytes(Charset.forName("utf-8"))), userConfigFile);
    }

}

class MyConstant extends Constant {

    public final static String FILE_TYPE_DTO = "DTO";

    public final static String FILE_TYPE_VO = "VO";

    public final static String DTO_TEMPLATE_NAME = "dto.java.btl";

    public final static String VO_TEMPLATE_NAME = "vo.java.btl";

}
